//----------------------------------------------------------------------------
//  ,-.
// /_\___ ,   CA Framework
// '  ___\    (C) Riccardo Capra - http://riccar.do
//  //   \\
//----------------------------------------------------------------------------

#include "NjExample01.h"
#include "NjAtomicStackTest.h"
#include "NjTestUnit.h"
#include "NjRandUint.h"
#include "NjExampleRandGenerator.h"
#include "NjSharedSyncObjectConstants.h"

//----------------------------------------------------------------------------
class NjAtomicStackTest::Static
{
public:
	static void RunSingleThreaded();

private:

	NJ_DISABLE_INSTANCE(Static);
};
//----------------------------------------------------------------------------
void NjAtomicStackTest::Run()
{
	Static::RunSingleThreaded();
}
//----------------------------------------------------------------------------
void NjAtomicStackTest::Static::RunSingleThreaded()
{
	NJ_TEST_UNITS_GROUP("NjAtomicStack");
		NJ_TEST_UNIT("All methods (single threaded)")
			static const NjUint ITERATIONS=1000000*NjSharedSyncObjectConstants::ITERATIONS_FACTOR;
			static const NjUint MAX_DEPTH=100;
			
			enum Operation
			{
				METH_PUSH,
				METH_POP,
				METH_FLUSH,
				TOT_OPERATIONS,
			};
			
			enum FlushOperation
			{
				METH_FLUSH_ONLY,
				METH_FLUSH_PUSH_RANGE_1,
				METH_FLUSH_PUSH_RANGE_2,
				TOT_FLUSH_OPERATIONS,
			};
			
			enum Stack
			{
				STACK_TEST,
				STACK_SPARE,
				TOT_STACKS
			};
			
			typedef NjAtomicStack::Node * NodePtr;
			
			NodePtr * const pSimpleStackNodes(NjNewArray(NodePtr[MAX_DEPTH]));
			NodePtr * pSimpleStackPos(pSimpleStackNodes);
			NjUint uAllocNodes(0);
			
			NjRandUint<EXAMPLE_RAND_GENERATOR> kRand;
			NjUint uIteration(ITERATIONS);
			NjAtomicStack aAtomicStack[TOT_STACKS];

			while (uIteration>0)
			{
				--uIteration;
				NjUint const uOperation(kRand()%10);
				Operation eOperation;

				if (uOperation<5 && pSimpleStackPos<pSimpleStackNodes+MAX_DEPTH)
					eOperation=METH_PUSH;
				else if (uOperation<9)
					eOperation=METH_POP;
				else
					eOperation=METH_FLUSH;
				
				switch(eOperation)
				{
					case METH_PUSH:
					{
						NjAtomicStack::Node * pNode(aAtomicStack[STACK_SPARE].Pop());
						
						if (pNode==NJ_NULL)
						{
							NJ_TEST(pSimpleStackNodes+uAllocNodes==pSimpleStackPos);
							pNode=NjNew(NjAtomicStack::Node);
							++uAllocNodes;
						}
						
						(*pSimpleStackPos)=pNode;
						++pSimpleStackPos;
						aAtomicStack[STACK_TEST].Push(pNode);
						break;
					}

					case METH_POP:
					{
						NjAtomicStack::Node * pNode(aAtomicStack[STACK_TEST].Pop());
						
						if (pNode==NJ_NULL)
						{
							NJ_TEST(pSimpleStackPos==pSimpleStackNodes);
							break;
						}
						
						NJ_TEST(pSimpleStackPos>pSimpleStackNodes);
						--pSimpleStackPos;
						NJ_TEST((*pSimpleStackPos)==pNode);
						
						aAtomicStack[STACK_SPARE].Push(pNode);
						break;
					}

					case METH_FLUSH:
					{
						NjAtomicStack::FlushNode * const pFlushNode(aAtomicStack[STACK_TEST].Flush());

						if (pFlushNode==NJ_NULL)
						{
							NJ_TEST(pSimpleStackPos==pSimpleStackNodes);
							break;
						}

						NjAtomicStack::FlushNode * pLastFlushNode;
						NodePtr * pSimpleStackPos2(pSimpleStackPos);
						NjAtomicStack::FlushNode * pFlushNodePos(pFlushNode);

						do
						{
							pLastFlushNode=pFlushNodePos;

							NJ_TEST(pSimpleStackPos2>pSimpleStackNodes);
							--pSimpleStackPos2;
							NJ_TEST((*pSimpleStackPos2)==pFlushNodePos->GetNode());

							pFlushNodePos=pFlushNodePos->GetNext();
						}
						while (pFlushNodePos!=NJ_NULL);

						NJ_TEST(pSimpleStackPos2==pSimpleStackNodes);

						FlushOperation const eFlushOperation(static_cast<FlushOperation>(kRand()%TOT_FLUSH_OPERATIONS));

						switch(eFlushOperation)
						{
							case METH_FLUSH_ONLY:
							{
								aAtomicStack[STACK_SPARE].PushRange(pFlushNode, pLastFlushNode);
								pSimpleStackPos=pSimpleStackNodes;
								break;
							}

							case METH_FLUSH_PUSH_RANGE_1:
							{
								NjUint const uStackLength(pSimpleStackPos-pSimpleStackNodes);
								NjUint uSkip(kRand()%uStackLength);
								pSimpleStackPos-=uSkip;
								pLastFlushNode=NJ_NULL;

								pFlushNodePos=pFlushNode;
								while (uSkip>0)
								{
									pLastFlushNode=pFlushNodePos;
									--uSkip;
									pFlushNodePos=pFlushNodePos->GetNext();
								}

								if (pLastFlushNode!=NJ_NULL)
									aAtomicStack[STACK_SPARE].PushRange(pFlushNode, pLastFlushNode);

								aAtomicStack[STACK_TEST].PushRange(pFlushNodePos);

								break;
							}

							case METH_FLUSH_PUSH_RANGE_2:
							{
								NjUint const uStackLength(pSimpleStackPos-pSimpleStackNodes);
								NjUint uSize(kRand()%uStackLength);
								NjUint uSkip(kRand()%(uStackLength-uSize));
								++uSize;

								NjAtomicStack::FlushNode * pLastFlushNode2(NJ_NULL);
								pFlushNodePos=pFlushNode;
								while (uSkip>0)
								{
									pLastFlushNode2=pFlushNodePos;
									--uSkip;
									pFlushNodePos=pFlushNodePos->GetNext();
								}

								NjAtomicStack::FlushNode * pFlushNodeEnd(pFlushNodePos);
								pSimpleStackPos=pSimpleStackNodes+uSize;
								for(;;)
								{
									--uSize;
									pSimpleStackNodes[uSize]=pFlushNodeEnd->GetNode();
									if (uSize<=0)
										break;

									pFlushNodeEnd=pFlushNodeEnd->GetNext();
								}

								if (pLastFlushNode2!=NJ_NULL)
									aAtomicStack[STACK_SPARE].PushRange(pFlushNode, pLastFlushNode2);

								if (pFlushNodeEnd!=pLastFlushNode)
									aAtomicStack[STACK_SPARE].PushRange(pFlushNodeEnd->GetNext(), pLastFlushNode);

								aAtomicStack[STACK_TEST].PushRange(pFlushNodePos, pFlushNodeEnd);

								break;
							}
					
							default:
							{
								NJ_TEST_FAIL();
								break;
							}
						}

						break;
					}
					
					default:
					{
						NJ_TEST_FAIL();
						break;
					}
				}
			}
			
			NjAtomicStack * pAtomicStack(aAtomicStack+NJ_LENGTH_OF(aAtomicStack));
			while (pAtomicStack>aAtomicStack)
			{
				--pAtomicStack;
				
				NjAtomicStack::FlushNode * pFlushNode(pAtomicStack->Flush());
				while (pFlushNode!=NJ_NULL)
				{
					NjAtomicStack::Node * const pNode(pFlushNode->GetNode());
					pFlushNode=pFlushNode->GetNext();
					NjDelete(pNode);
					--uAllocNodes;
				}
			}
			
			NjDeleteArray(pSimpleStackNodes);
			NJ_TEST(uAllocNodes==0);
		NJ_TEST_UNIT_END();
	NJ_TEST_UNITS_GROUP_END();
}
//----------------------------------------------------------------------------
